package ClientsAndHotels;

import jade.core.AID;
import jade.core.behaviours.CyclicBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import ClientsAndHotels.types.DiedAgentData;
import ClientsAndHotels.types.OrderRequest;
import ClientsAndHotels.types.OrderResponse;

public class MediatorAgent extends BaseAgent {

    public static final String TYPE = "Mediator";

    enum Action {
        RECEIVE_QUERY_FROM_CLIENT,
        SEND_QUERY_TO_HOTEL,
        RECEIVE_ANSWER_FROM_HOTEL,
        SEND_ANSWER_TO_CLIENT
    }

    private Action action = Action.RECEIVE_QUERY_FROM_CLIENT;

    private ArrayList<AID> knownHotels = new ArrayList<>();

    @Override
    protected void setup() {

        notifyGuiAboutDie(DiedAgentData.TYPE_MEDIATOR);
        
        log("Przybył nowy mediator " + getAID().getName() + ".");
        // Zarejestruj się na żółtych stronach
        registerOnYellowPages(TYPE, "JADE-mediator-args");

        addBehaviour(new HotelRegisterServer());

        addBehaviour(new ClientRequestServer());
    }

    /**
     * Inner class HotelHelloServer.
     */
    private class HotelRegisterServer extends CyclicBehaviour {

        @Override
        public void action() {
            MessageTemplate mt = MessageTemplate
                    .MatchPerformative(ACLMessage.INFORM);
            ACLMessage msg = myAgent.receive(mt);
            if (msg != null) {
                String message = msg.getContent();
                if (message.equals(HotelAgent.MESSAGE_HELLO)) {
                    log("Hotel %s rejestruje się", msg.getSender().getLocalName());
                    if (!knownHotels.contains(msg.getSender()))
                        knownHotels.add(msg.getSender());
                } else if (message.equals(HotelAgent.MESSAGE_GOODBYE)) {
                    knownHotels.remove(msg.getSender());
                    log("Hotel %s wyrejestrowuje się", msg.getSender().getLocalName());
                }
            } else {
                block();
            }
        }
    }

    /**
     * Inner class ClientRequestServer.
     */
    private class ClientRequestServer extends CyclicBehaviour {

        // Schemat wiadomości odbieranych
        private MessageTemplate mt;
        // aktualne zlecenie
        private OrderRequest orderReq;
        // odpowiedź na zlecenie
        private HashMap<AID, OrderResponse> orderResps = new HashMap<>();
        // hasło do odpowiedzi dla klienta
        private ACLMessage replyMessage;
        // próby
        private int attempts;
        
        @Override
        public void action() {
            try {
                switch (action) {
                case RECEIVE_QUERY_FROM_CLIENT:
                    orderResps = new HashMap<>();
                    mt = MessageTemplate.and(
                            MessageTemplate.MatchPerformative(ACLMessage.CFP),
                            MessageTemplate.MatchLanguage("JavaSerialization"));
                    ACLMessage msg = myAgent.receive(mt);
                    if (msg != null) {
                        log("Otrzymałem żądanie listy hoteli od " + msg.getSender().getLocalName() + ".");
                        orderReq = (OrderRequest) msg.getContentObject();
                        replyMessage = msg.createReply();
                        if (knownHotels.isEmpty()) {
                            orderResps.clear();
                            action = Action.SEND_ANSWER_TO_CLIENT;
                        } else if (orderReq == null) {
                            action = Action.RECEIVE_QUERY_FROM_CLIENT;
                        } else {
                            action = Action.SEND_QUERY_TO_HOTEL;
                        }
                    } else {
                        block();
                    }
                    break;
                case SEND_QUERY_TO_HOTEL:
                    ACLMessage msgReq = new ACLMessage(ACLMessage.REQUEST);
                    for (AID aid : knownHotels) {
                        msgReq.addReceiver(aid);
                    }
                    msgReq.setContentObject(orderReq);
                    msgReq.setLanguage("JavaSerialization");
                    msgReq.setConversationId("offer-search");
                    msgReq.setReplyWith("request" + System.currentTimeMillis());
                    myAgent.send(msgReq);
                    log("Wysyłam żądanie do wszystkich hoteli.");
                    // Przygotuj schemat wiadomości do odbioru informacji o
                    // hotelach
                    mt = MessageTemplate.and(MessageTemplate.and(
                            MessageTemplate.and(MessageTemplate
                                    .MatchConversationId("offer-search"),
                                    MessageTemplate.MatchInReplyTo(msgReq
                                            .getReplyWith())), MessageTemplate
                                    .MatchLanguage("JavaSerialization")),
                            MessageTemplate
                                    .MatchPerformative(ACLMessage.PROPOSE));
                    attempts = 0;
                    action = Action.RECEIVE_ANSWER_FROM_HOTEL;
                    break;
                case RECEIVE_ANSWER_FROM_HOTEL:
                    // Odbierz informacje o ofertach od hoteli
                    ACLMessage reply = myAgent.receive(mt);
                    if (reply != null) {
                        orderResps.put(reply.getSender(),
                                (OrderResponse) reply.getContentObject());
                        log("Zbieram hotele do listy. " + orderResps.size()
                                + "/" + knownHotels.size());
                        if (orderResps.size() >= knownHotels.size()) {
                            action = Action.SEND_ANSWER_TO_CLIENT;
                        }
                    } else {
                        block(1000);
                        if (++attempts > knownHotels.size())
                            action = Action.SEND_ANSWER_TO_CLIENT;
                    }
                    break;
                case SEND_ANSWER_TO_CLIENT:
                    log("Wysyłam listę hoteli i ich pokojów do " + ((AID)replyMessage.getAllReceiver().next()).getLocalName() + ".");
                    replyMessage.setPerformative(ACLMessage.PROPOSE);
                    ArrayList<OrderResponse> r = new ArrayList<>(orderResps.values());
                    replyMessage.setContentObject(r);
                    myAgent.send(replyMessage);
                    orderResps = null;
                    orderReq = null;
                    replyMessage = null;
                    action = Action.RECEIVE_QUERY_FROM_CLIENT;
                    break;
                }
            } catch (UnreadableException | IOException e) {
                System.err.println(getLocalName() + " catched exception "
                        + e.getMessage());
            }
        }
    }
}
